﻿using DotLiquid;
using MiniExcelLibs;
using Newtonsoft.Json.Linq;

namespace excel2bean
{
    internal class Parser
    {
        private Parser(string sheet, string path, Dictionary<string, string> colIdxMap, string majorKey, string majorKeyCol, List<dynamic> rows)
        {
            this.sheet = sheet;
            this.path = path;
            _colIdxMap = colIdxMap;
            this.majorKey = majorKey;
            this.majorKeyCol = majorKeyCol;
            _rows = rows;

            _logInfo = $"文件：{path}\t 工作簿：{sheet}\t";
        }

        /// <summary>
        /// 工作簿名
        /// </summary>
        public readonly string sheet;

        /// <summary>
        /// 文件路径
        /// </summary>
        public readonly string path;

        /// <summary>
        /// 列位置索引
        /// </summary>
        private readonly Dictionary<string, string> _colIdxMap;

        /// <summary>
        /// 主键
        /// </summary>
        public readonly string majorKey;

        /// <summary>
        /// 主键列
        /// </summary>
        public readonly string majorKeyCol;

        private List<dynamic> _rows;

        private readonly string _logInfo;

        /// <summary>
        /// 分割字符
        /// </summary>
        private static readonly char[] _separators = new char[] { '#', '|' };

        /// <summary>
        /// 读取工作簿
        /// </summary>
        /// <param name="path"></param>
        /// <param name="sheet"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static Parser Make(string path, string sheet)
        {
            List<dynamic> rows = MiniExcel.Query(path, false, sheet).ToList();

            if (rows.Count < 3)
            {
                throw new Exception($"文件：{path}\t 工作簿：{sheet}\t 行数不对");
            }

            //列索引
            Dictionary<string, string> colIdxMap = new Dictionary<string, string>();

            //主键列名
            string majorKey = string.Empty;
            string majorKeyCol = string.Empty;

            //字段名
            dynamic headRow = rows[1];
            foreach (var kv in headRow)
            {
                if (kv.Value == null)
                {
                    continue;
                }
                string key = kv.Value;

                //第一列为主键
                if (string.IsNullOrEmpty(majorKey))
                {
                    majorKey = key;
                    majorKeyCol = kv.Key;
                }

                if (colIdxMap.ContainsKey(key))
                {
                    throw new Exception($"文件：{path}\t 工作簿：{sheet}\t 存在重复列：{key}！");
                }

                colIdxMap[key] = kv.Key;
            }

            if (string.IsNullOrEmpty(majorKey))
            {
                throw new Exception($"文件：{path}\t 工作簿：{sheet}\t 没有主键！");
            }

            return new Parser(sheet, path, colIdxMap, majorKey, majorKeyCol, rows);
        }

        /// <summary>
        /// 类型描述类
        /// </summary>
        internal class FieldType : ILiquidizable
        {
            /// <summary>
            /// 基础类型
            /// </summary>
            public readonly Type type;

            /// <summary>
            /// 列表嵌套层级
            /// </summary>
            public readonly int nesting;

            /// <summary>
            /// 输入
            /// </summary>
            public readonly string content;

            /// <summary>
            /// 列名
            /// </summary>
            public readonly string field;

            private readonly string desc;

            internal FieldType(string content, string desc, string field)
            {
                this.content = content;
                this.field = field;
                this.desc = desc;

                string[] arr = content.Split('[');

                int len = arr.Length;

                if (len > _separators.Length + 1)
                {
                    throw new Exception($"列数组嵌套太多了 {content}");
                }
                else if (len > 1)
                {
                    //数组
                    for (int i = 1; i < len; i++)
                    {
                        if (arr[i] != "]")
                        {
                            throw new Exception($"列类型有问题 {content}");
                        }
                    }
                    nesting = len - 1;
                }

                switch (arr[0])
                {
                    case "bool":
                        this.type = typeof(bool);
                        break;
                    case "string":
                    case "char":
                        this.type = typeof(string);
                        break;
                    case "short":
                    case "ushort":
                    case "int":
                    case "uint":
                    case "long":
                    case "ulong":
                        this.type = typeof(long);
                        break;
                    case "float":
                    case "double":
                    case "decimal":
                        this.type = typeof(decimal);
                        break;
                    default:
                        throw new Exception($"意外的类型：{arr[0]}!");
                }
            }

            public object ToLiquid()
            {
                return new
                {
                    content,
                    field,
                    desc
                };
            }
        }

        /// <summary>
        /// 表格内容解析json
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        internal (JArray jroot, Dictionary<string, FieldType> types) Parse()
        {
            //类型行
            IDictionary<string, dynamic> typeRow = _rows[2];
            IDictionary<string, dynamic> descRow = _rows[3];

            //解析类型
            Dictionary<string, FieldType> types = new Dictionary<string, FieldType>();
            foreach (KeyValuePair<string, string> kv in _colIdxMap)
            {
                string cols = kv.Value;
                string typeC = typeRow[cols];
                if (string.IsNullOrEmpty(typeC))
                {
                    if (cols == majorKeyCol)
                    {
                        throw new Exception($"{_logInfo} 主键类型为空");
                    }
                    continue;
                }
                try
                {
                    types[cols] = new FieldType(typeC.Trim(), descRow[cols], kv.Key);
                }
                catch (Exception e)
                {
                    throw new Exception($"{_logInfo} {e.Message}");
                }
            }

            JArray root = new JArray();

            //从第四行开始
            for (int i = 4; i < _rows.Count; i++)
            {
                JObject bean = new JObject();

                IDictionary<string, object> row = _rows[i];

                //跳过没有主键列的行
                object majorKey = row[majorKeyCol];
                if (majorKey == null)
                {
                    continue;
                }

                foreach (KeyValuePair<string, FieldType> kv in types)
                {
                    FieldType fieldType = kv.Value;

                    string? content = Convert.ToString(row[kv.Key]);

                    if (content == null)
                    {
                        throw new Exception($"意外的单元格内容{row[kv.Key]}");
                    }
                    else if (content == string.Empty)
                    {
                        continue;
                    }

                    try
                    {
                        string field = fieldType.field;
                        Type t = fieldType.type;
                        if (fieldType.nesting > 0)
                        {
                            //是数组 进拆分
                            bean.Add(field, Split(fieldType.nesting, content, t));
                        }
                        else
                        {
                            //不是数组 直接转成目标类型
                            if (string.IsNullOrWhiteSpace(content))
                            {
                                //如果单元格内容是空的 ，并且单元格是值类型，给个默认值
                                if (t.IsValueType)
                                {
                                    bean.Add(field, JToken.FromObject(Activator.CreateInstance(t)));
                                }
                            }
                            else
                            {
                                bean.Add(field, JToken.FromObject(Convert.ChangeType(content, t)));
                            }
                        }
                    }
                    catch (FormatException)
                    {
                        throw new Exception($"{_logInfo} 单元格:[{content}]和类型[{fieldType.content}]不匹配");
                    }
                }

                root.Add(bean);
            }

            return (root, types);
        }

        internal string GetDesc()
        {
            IDictionary<string, object> row = _rows[0];
            return row.TryGetValue("A", out object? desc) ? (string)desc : string.Empty;
        }

        /// <summary>
        /// 拆分数组字段
        /// </summary>
        /// <param name="nesting"></param>
        /// <param name="content"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        private JArray Split(int nesting, string content, Type t)
        {
            JArray array = new JArray();

            //获取拆分字符
            char separator = _separators[--nesting];
            string[] arr = content.Split(separator);

            foreach (string s in arr)
            {
                if (nesting > 0)
                {
                    //还有数组 递归拆分
                    array.Add(Split(nesting, s, t));
                }
                else
                {
                    //没数组了 转类型
                    array.Add(Convert.ChangeType(s, t));
                }
            }

            return array;
        }
    }
}
